package im.composer.media.sound.codec;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.sound.sampled.spi.AudioFileReader;

import org.kc7bfi.jflac.FLACDecoder;
import org.kc7bfi.jflac.frame.Frame;
import org.kc7bfi.jflac.metadata.StreamInfo;
import org.kc7bfi.jflac.util.ByteData;
import org.tritonus.share.sampled.AudioFileTypes;
import org.tritonus.share.sampled.FloatInputStream;

import im.composer.media.audio.stream.AudioByteBuffer;
import im.composer.media.sound.codec.seek.FLACSeekSupport;

public class FLACFileReader extends AudioFileReader implements PathAwareAudioFileReader {

	@Override
	public AudioFileFormat getAudioFileFormat(InputStream stream) throws UnsupportedAudioFileException, IOException {
		FLACDecoder decoder = new FLACDecoder(stream);
		StreamInfo stream_info = decoder.readStreamInfo();
		return new AudioFileFormat(new AudioFileTypes("FLAC", "flac"), stream_info.getAudioFormat(), (int) stream_info.getTotalSamples());
	}

	@Override
	public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException {
		return getAudioFileFormat(url.openStream());
	}

	@Override
	public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException {
		return getAudioFileFormat(new FileInputStream(file));
	}

	private AudioInputStream getAudioInputStream(FLACDecoder decoder, FLACSeekSupport seek_support) throws UnsupportedAudioFileException, IOException {
		StreamInfo si = decoder.readStreamInfo();
		AudioFormat format = si.getAudioFormat();
		AudioByteBuffer abb = new AudioByteBuffer() {
			ByteData bd = null;

			@Override
			public void requestNewBuffer() throws IOException {
				if (decoder.isEOF()) {
					close();
				} else {
					Frame frame = decoder.readNextFrame();
					bd = decoder.decodeFrame(frame, bd);
					byte[] b_arr = Arrays.copyOf(bd.getData(), bd.getLen());
					offer(b_arr);
				}
			}

		};
		AudioInputStream ais = new AudioInputStream(abb, format, si.getTotalSamples());
		FloatInputStream fis = new FloatInputStream(ais, format, si.getTotalSamples()) {

			@Override
			public boolean supportsSeek() {
				return seek_support != null && seek_support.isEnabled();
			}

			@Override
			public void seek(long sample_position) throws IOException {
				synchronized (this) {
					abb.clear();
					seek_support.seek(sample_position);
				}
			}
		};
		seek_support.setDecoder(decoder);
		seek_support.setAudioByteBuffer(abb);
		seek_support.setOutput(fis);
		return fis;
	}

	@Override
	public AudioInputStream getAudioInputStream(InputStream stream) throws UnsupportedAudioFileException, IOException {
		return getAudioInputStream(new FLACDecoder(stream), null);
	}

	@Override
	public AudioInputStream getAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException {
		return getAudioInputStream(url.openStream());
	}

	@Override
	public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException {
		Path path = Paths.get(file.toURI());
		SeekableByteChannel sbc = Files.newByteChannel(path);
		return getAudioInputStream(new FLACDecoder(sbc), new FLACSeekSupport(path,sbc));
	}

	@Override
	public AudioFileFormat getAudioFileFormat(Path path) throws UnsupportedAudioFileException, IOException {
		return getAudioFileFormat(Files.newInputStream(path));
	}

	@Override
	public AudioInputStream getAudioInputStream(Path path) throws UnsupportedAudioFileException, IOException {
		SeekableByteChannel sbc = Files.newByteChannel(path);
		return getAudioInputStream(new FLACDecoder(sbc), new FLACSeekSupport(path,sbc));
	}

}
